feat: unify smartui-storybook into smartui-cli (built-in storybook command + TUI) - #527
Draft
chaitanyas-maker wants to merge 13 commits into
Draft
feat: unify smartui-storybook into smartui-cli (built-in storybook command + TUI)#527chaitanyas-maker wants to merge 13 commits into
storybook command + TUI)#527chaitanyas-maker wants to merge 13 commits into
Conversation
chaitanyas-maker
force-pushed
the
feat/unify-storybook
branch
from
August 24, 2026 14:49
850ee15 to
6f67b26
Compare
…command Vendor the proven @lambdatest/smartui-storybook v1.1.32 engine into the single smartui binary (faithful relocation hitting the same /storybook/* backend), add config:create-storybook, and resolve the colliding-bin problem. One install, one binary. Version 5.0.0-unified.1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CwQRZwr64F9fLQAW4V1bWy
…ent, log-symbols) Gradient figlet banner + launch config box + end-of-run summary box (dashboard link, screenshot/approval/change counts) around the storybook command. Summary deferred to process 'beforeExit' so it lands after the engine's polling. Verified with a live baseline build (11 stories -> 33 screenshots) on SmartUI cloud. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CwQRZwr64F9fLQAW4V1bWy
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CwQRZwr64F9fLQAW4V1bWy
Two bugs that broke the URL-mode capture path (surfaced by live URL testing, confirmed by @sushobhit-lt review on PR #1): - storybook.ts: merge root global flags via optsWithGlobals() so `--config` actually reaches the engine (local -c/--config collided with the global one), letting validateConfig normalize viewports->resolutions. - dom.cjs: declare `dom`/`clone`/`element` with const — they were implicit globals that throw ReferenceError once bundled into strict mode (same class as the earlier res/filename/githubURL fixes; these three were missed). - storybook.cjs: guard the URL-mode resolutions loop with a viewports fallback so `smartui storybook <url>` with no -c flag doesn't crash on the default config (per review). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CwQRZwr64F9fLQAW4V1bWy
…TE-24909) URL mode fetched stories.json only. Storybook 8 dropped that file and serves index.json instead, so the request 404s, discovery reports "Cannot fetch stories" and the run ends with 0 screenshots. DIR mode already read either file. Try index.json first and fall back to stories.json, accepting both payload shapes (entries for v8, stories for v7) and both title fields (title for v8, kind for v7). Verified against a real Storybook 8.6 index (17 stories) served with index.json only and with stories.json only: before the change the first case failed with 404, after it resolves 17 stories from either file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017LYhe6ASMZLy7LSuAJ8pqQ
…po (TE-24912, TE-24910) Two auth-adjacent problems in the vendored engine: PROJECT_NAME (TE-24912). The engine only accepted a pre-issued PROJECT_TOKEN, while the rest of the CLI accepts PROJECT_NAME and auto-creates the project. Resolve PROJECT_NAME through the same /visualui/1.0/token/verify call the core client uses, then continue on the resulting token, so downstream code is unchanged. No git repo (TE-24910). The duplicate-build check keys off branch and commit. Outside a git repo both are empty, the API rejects the call, and the CLI prints "Cannot fetch latest build of the project. Error: Request failed with status code 401", which reads like an auth failure. Skip the check with a message naming the real cause, matching how the core CLI skips git details when it is not a git repo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017LYhe6ASMZLy7LSuAJ8pqQ
…nch failures (TE-24911) This branch switched the lockfile to pnpm, and pnpm 10 blocks dependency install scripts by default. puppeteer's postinstall never runs, so its Chromium is never downloaded and URL mode dies on a WS endpoint timeout that says nothing about the missing browser. Declare the packages whose install scripts are required in pnpm.onlyBuiltDependencies, and wrap puppeteer.launch so a failure names the likely cause and the command that fixes it. The lockfile change is the playwright bump that came in from stage. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017LYhe6ASMZLy7LSuAJ8pqQ
… refusing (TE-23033) config:create and config:create-storybook both default to .smartui.json, so running the second after the first only produced "SmartUI Storybook config already exists". The schema already allows one file to carry both a web and a storybook block, so write the block into the existing file instead. An existing storybook block is still left alone and reported. Also give the storybook browsers uniqueItems rule its own message, which the enum catch-all was swallowing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017LYhe6ASMZLy7LSuAJ8pqQ
…ile (TE-23033) TE-23033 was fixed in one direction only. `config:create-storybook` merges into an existing .smartui.json, but running the two generators the other way round still hit "Error: SmartUI Config already exists" and refused, even though the file held only a storybook block and the schema explicitly allows one file to carry both. config:create now adds the missing default keys when the existing file has a storybook block and no web block, and otherwise refuses exactly as before. Every default key is copied, not just `web`: the top-level ones (waitForTimeout, smartIgnore and friends) are part of a web config, and copying only `web` and `mobile` produced a different file depending on which order the two commands ran in. Verified against the built binary: both orders now yield the same key set (allowedHostnames, enableJavaScript, mobile, showRenderErrors, smartIgnore, storybook, waitForTimeout, web). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015zE3Pt3jgMUMS3xNuej7dF
…E-24909) URL-mode discovery tries index.json first and falls back to stories.json, but DIR mode still preferred the legacy stories.json. When a static directory carries both (a Storybook 7 build rebuilt as 8 leaves the old file behind) the two modes read different files and could snapshot different story sets from the same Storybook. DIR mode now prefers index.json with the same legacy fallback, and accepts either payload key in either file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015zE3Pt3jgMUMS3xNuej7dF
… a bare token error
Two misleading-error bugs of the same family as TE-24910, both found running the
negative cases on the default code path.
1. The duplicate-build check ran before the target was validated, and it exits the
run. So `smartui storybook ./does-not-exist` reported "Build with commit ... on
branch ... already exists" and told the user to pass --force-rebuild. The
directory and URL validators were correct all along, they were simply never
reached unless --force-rebuild happened to skip the check in front of them.
Target validation now runs first. The checks are read-only and idempotent, so
storybook() re-running them costs nothing beyond one extra request in URL mode.
Now on the default path:
./does-not-exist -> No directory found: ./does-not-exist
empty dir -> Given directory is not a storybook static directory
dead URL -> Connection to storybook not established
and a valid target still falls through to the duplicate-build check unchanged.
2. /token/verify answers HTTP 200 even when project creation fails, carrying the
reason in the body. Resolution failure printed only "project token not
received", which reads like a credentials problem. It now prints the server's
own message, e.g. "Failed to create project: project creation failed with
status 502 Bad Gateway", which is what was actually happening.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015zE3Pt3jgMUMS3xNuej7dF
…surface
Closes the two engineering follow-ups left open on TE-21062.
Tests. The repository had no test framework at all, so this adds vitest and 64
tests over the parts of this branch that actually broke in QA:
- storybook config schema, 41 cases including the negative ones (browsers,
viewport bounds and the 5-viewport cap, waitForTimeout bounds, backgroundTheme,
chunkSize, the customViewports styles-XOR-waitForTimeout and
stories-XOR-exclude rules, unknown keys at both levels), plus a guard on
snapshot ignoreColors so the TE-24908 stale-base failure cannot come back
unnoticed
- story discovery, covering index.json, the legacy stories.json fallback, an
empty index.json falling through rather than yielding a zero-story run, docs
filtering for both Storybook generations, and the DIR/URL index preference
- the config generators in both orders
The suite caught a real defect in the config:create fix while it was being
written: copying only `web` and `mobile` made the output depend on which order the
two generators ran in.
@ts-nocheck. src/commander/storybook.ts carried it because the vendored .cjs
imports had no types. It now has hand-written declarations
(src/storybookVendor/index.d.cts, src/lib/tui.d.cts) that describe the engine and
TUI surfaces without touching the vendored JavaScript, which stays a faithful
relocation on purpose.
The file typechecks clean under strict plus noUncheckedIndexedAccess. Two latent
issues surfaced while typing it: kv() drops empty values, so indexing [0] after it
can be undefined, and the catch bound a bare any.
Typecheck scope. `tsc --noEmit` reports 251 pre-existing errors across files this
branch never touches, and the committed tsconfig cannot even be used directly
(module CommonJS with moduleResolution NodeNext is rejected). tsup strips types
without checking them, so this has never been caught. Rather than boil that ocean,
tsconfig.storybook.json checks just this branch's surface, wired up as
`pnpm run typecheck:storybook`.
fetchStoryIndex is exported so the TE-24909 fix can be covered directly.
pnpm test -> 64 passed
pnpm run typecheck:storybook -> 0 errors
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015zE3Pt3jgMUMS3xNuej7dF
chaitanyas-maker
force-pushed
the
feat/unify-storybook
branch
from
September 3, 2026 10:27
6f67b26 to
25fc58e
Compare
…and stop leaking the access key
Adversarial pass over the storybook command. Six defects, found by testing the paths the
happy-path runs never touch.
**Storybook served under a sub-path was broken twice over.** `new URL('index.json', base)`
drops the last path segment unless the base ends in a slash, so
`smartui storybook http://host/team/storybook` fetched `http://host/index.json`. Separately,
story URLs were built from `'/iframe.html?...'` with a leading slash, which resolves to the
origin root and discards the sub-path entirely, so *every* story URL was wrong for such a
deployment even when discovery happened to work. Validation passed first, so the run reported
"Connection to storybook established" and then quietly captured nothing. Both now resolve
against a normalised base. Verified end to end against a Storybook served under
/team/storybook, with and without a trailing slash.
**A malformed story index crashed or, worse, did not.** `entries: null` and a missing `entries`
key threw a raw TypeError; a null entry threw inside skipStory; and a string index was iterated
by character, producing stories named "0".."3" that were uploaded to the backend as if real.
Indexes are now validated in both DIR and URL discovery, junk entries are dropped, and a
truncated file reports "Could not parse" rather than a SyntaxError.
**The access key was printed to stdout.** A failed /token/verify answers "Authentication failed
for provided username: <user> and accessKey: <the real key>", and surfacing the server's message
put a live credential into stdout and therefore into CI logs. Added a redactor, applied both at
the point of use and as a catch-all over everything the engine prints. The backend echoing the
key at all is a separate problem and needs its own fix.
**Configs that are not objects.** An existing config containing a JSON array reported "Added
SmartUI Storybook config" while JSON.stringify silently dropped the added property and the file
was rewritten unchanged. A bare `null` crashed with an unhandled TypeError and a Node stack
trace. A leading BOM made a perfectly good file unreadable. All three now behave: BOM is
stripped, non-objects are refused by name without touching the file.
**Runs that ended via process.exit printed no summary.** beforeExit does not fire on
process.exit, which the engine uses for "No stories found", an unreachable Storybook and more,
so those runs trailed off after the launch box. Hooking 'exit' as well fixes it; writes from an
exit handler are synchronous and survive pipes, which was checked rather than assumed. Exit code
3, the engine's "a build already exists" signal, is now shown as a yellow skip rather than a red
failure. Failure messages are also extracted properly: an unreachable Storybook logs "Error:"
with nothing after it, which used to render an empty box.
**A build that captured nothing looked like a pass.** Zero screenshots still exits 0, so in CI it
is indistinguishable from a successful visual run. The exit contract is not changed here, but the
summary now says so in a yellow "complete, but empty" box.
Also bounded the stdout buffer the summary is parsed from. It held every byte the engine printed,
about 360 KB for 17 stories, which extrapolates to roughly 21 MB on a 1000-story Storybook, to
extract five numbers. Now a 64 KB tail with failure flags latched as lines stream past.
Tests: 102 passing, up from 64.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015zE3Pt3jgMUMS3xNuej7dF
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Folds
@lambdatest/smartui-storybookinto this CLI as a built-instorybookcommand, so one binary owns thesmartuiname. Both packages declarebin.smartuitoday, so installing one silently clobbers the other and users need two installs for one tool.Jira: TE-21062
State
Rebased onto stage
7e21426.5.0.0-unified.3. Mergeable, no conflicts. 102 tests passing.Draft on one count only: TE-27274 below is a backend issue outside this diff and needs an owner before this ships as
5.0.0.What is in here
The unification. Storybook lives at
src/storybookVendor/**as a faithful relocation of the shipping package, so the proven engine and its/storybook/*flow are unchanged.smartui storybook <url-or-dir>in URL and DIR mode,smartui config:create-storybook, astorybookblock inConfigSchema(listed inanyOftoo, since the top level isadditionalProperties:false), and a terminal UI that degrades gracefully without a TTY.Everything QA raised.
ignoreColorsrejected with HTTP 500. Not a product defect: the branch was cut from prod atc03c033and never picked upeed39d2. Fixed by the rebase, now test-covered.stories.jsononly, so Storybook 8 discovery 404'd. Triesindex.jsonfirst with a legacy fallback. DIR mode now prefers it too: it disagreed with URL mode when a static dir carried both files..smartui.json. Each now merges into the other's file, in either order.Then an adversarial pass, testing the paths happy-path runs never touch. Six more defects:
new URL('index.json', base)drops the last segment unless the base ends in a slash, sohttp://host/team/storybookfetchedhttp://host/index.json. Separately, story URLs used a leading/iframe.html, which resolves to the origin root and discards the sub-path, so every story URL was wrong even when discovery worked. Validation passed first, so the run said "Connection established" and then captured nothing.entries: nulland a missingentrieskey threw raw TypeErrors; a null entry threw insideskipStory; and a string index was iterated by character, producing stories named"0".."3"that were uploaded as if real./token/verifyechoes the caller's access key, so surfacing the server's message wrote a live credential into CI logs. Added a redactor at the point of use and as a catch-all over everything the engine prints. Endpoint fix tracked as TE-27291.JSON.stringifysilently dropped the added property; a barenullcrashed with a Node stack trace; a BOM made a valid file unreadable.process.exitprinted no summary, sincebeforeExitdoes not fire there. Now hooked onexittoo (verified such writes survive pipes). Exit code 3 is shown as a yellow skip, not a red failure, and failure text is extracted properly instead of rendering an empty box.Also bounded the stdout buffer the summary is parsed from: it held every byte printed, ~360 KB for 17 stories and ~21 MB extrapolated to 1000, to extract five numbers.
Tests and typing.
ignoreColorsregression guard.@ts-nocheckgone, replaced with declarations for the vendored surfaces. Typing it surfaced two latent issues:kv()drops empty values so indexing[0]can be undefined, and the catch bound a bareany.tsc --noEmitreports 251 pre-existing errors across untouched files, and the committed tsconfig cannot be used directly (module: CommonJSwithmoduleResolution: NodeNextis rejected). tsup strips types without checking them. Rather than boil that ocean,tsconfig.storybook.jsongates this branch's surface viapnpm run typecheck:storybook.Verified
pnpm test/typecheck:storybook/build/npm packsmartuibinary, all commands work--env stageroutingCOLUMNS=40,CI=true,TERM=dumb,NO_COLOR=1, no TTYBackend, outside this diff
Total Screenshots: 0. Discovery is confirmed working (17 of 17 stories fromindex.json), so it is the render endpoint, not TE-24909. This is the one thing keeping this a draft./token/verifyechoes the caller's access key. Mitigated here, but the endpoint should stop./token/verifyanswers HTTP 200 on failure and can create a project anyway. Transient. An earlier claim on this ticket that DIR mode was broken has been withdrawn: DIR mode works.Still open for the team
Versioning and deprecation. Still proposing
5.0.0MAJOR plus a shim on@lambdatest/smartui-storybook. TE-27246 adds a constraint: that package is about to become the Storybook addon catalog listing (LambdaTest/smartui-storybook#130), so the shim must stay a working install, and the catalog metadata should be mirrored onto this package at 5.0.0.🤖 Generated with Claude Code
https://claude.ai/code/session_015zE3Pt3jgMUMS3xNuej7dF